;; The first three lines of this file were inserted by DrRacket. They record metadata
;; about the language level of this file in a form that our tools can easily process.
#reader(lib "htdp-intermediate-reader.ss" "lang")((modname |12|) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f ())))

;; 1. Balls in a Box
;; horizontally moving balls
;; with provision of selecting each ball 
;; and dragging and placing them 
;; anywhere on the canvas and adding new balls

(require rackunit)

(require 2htdp/universe)

(require 2htdp/image)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Data Definition

(define-struct ball (pos-x pos-y speed selected?))
;; Ball is a (make-ball Number Number Number Boolean)
;; Interpretation: 
;; a ball has the following properties
;; pos-x is the x-coordinate of the ball
;; pos-y is the x-coordinate of the ball
;; speed is the speed of the ball.
;; selected? is a flag that denotes whether 
;; the ball is selected or not.

;; Template:
;; ball-fn : Ball -> ??
;; (define (ball-fn a-ball)
;;    ((... (ball-pos-x a-ball)) (... (ball-pos-y a-ball))
;;     (... (ball-speed a-ball)) (... (ball-selected? a-ball)))))   

;; World is one of
;; -- (cons Ball empty)
;;    Interp: having one ball
;; -- (cons Ball World)
;;    Interp: having one or more balls

;; Template:
;; world-fn : World -> ??
;; (define (world-fn l-balls)
;;   (local (; contract for combiner
;;           (define (combiner ...) ...))
;;     (abstraction combiner l-balls)))

;; foldr : (X Y -> Y) Y [List-of X] -> Y
;; compute the result of applying f from right to left to all of
;; alox and base, that is, apply f to
;; the last item in alox and base,
;; the penultimate item and the result of the first step,
;; and so on up to the first item
;; (foldr f base (list x-1 ... x-n)) = (f x-1 ... (f x-n base))
;; (define (foldr f base alox) ...)

;; map : (X -> Y) [List-of X] -> [List-of Y]
;; construct a list by applying f to each item on alox
;; (map f (list x-1 ... x-n)) = (list (f x-1) ... (f x-n))
;; (define (map f alox) ...)

;; A BallKeyEvent is one of 
;; -- "n"        
;;    Interp: a ball is added to the world when 'n' key is pressed
;; -- any other
;;    Interp: world is unaffected when any other key is pressed
;;
;; Template:
;; key-fn : World BallKeyEvent -> ??
;;(define (key-fn l-balls kev) 
;;  (cond [(key=? kev "n")(... l-balls)]
;;        [else l-balls]))       

;; A BallMouseEvent is one of 
;; -- "button-down"
;;    Interp: ball is selected on "button-down" event
;; -- "button-up"
;;    Interp: ball is deselected on "button-up" event
;; -- "drag"
;;    Interp: selected ball is dragged on "drag" event
;; -- "leave"
;;    Interp: ball is left deselected on "leave" event
;; -- any other
;;    Interp: world is unaffected on all other events
;;
;; Template:
;; mouse-fn : World BallMouseEvent -> ??
;; (define (mouse-fn l-balls x y mev)
;;   (cond [(mouse=? mev "button-down") (... l-balls x y)]
;;         [(mouse=? mev "button-up") (... l-balls)]
;;         [(mouse=? mev "drag") (... l-balls x y)]
;;         [(mouse=? mev "leave") (... l-balls)]
;;         [else l-balls]))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; run-world! Number -> World
;; Takes the speed of the balls, in pixels/tick, and runs the world.
;; Returns the final state of the world (list of balls).
(define (run-world! speed)
  (big-bang (cons (make-ball INIT-POS-X INIT-POS-Y speed false) empty) 
            (on-key add-ball) (on-tick animate-world 0.25) 
            (on-mouse select-n-drag) (on-draw render)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Constants

;; attributes of canvas
(define CANVAS-WIDTH 300)
(define CANVAS-HEIGHT 200)
(define CANVAS (empty-scene CANVAS-WIDTH CANVAS-HEIGHT))

;; attributes of ball
(define INIT-POS-X (/ CANVAS-WIDTH 2))
(define INIT-POS-Y (/ CANVAS-HEIGHT 2))
(define BALL-SELECTED (circle 20 "solid" "red"))
(define BALL-DESELECTED (circle 20 "outline" "red"))
(define BALL-RADIUS (/ (image-width BALL-DESELECTED) 2))

;; examples
;; used for testing

(define ball-1-deselected (make-ball (+ INIT-POS-X 30) 
                                     (+ INIT-POS-Y 30) 20 false))
(define ball-2-deselected (make-ball INIT-POS-X 
                                     INIT-POS-Y 20 false))
(define ball-1-selected (make-ball (+ INIT-POS-X 30) 
                                   (+ INIT-POS-Y 30) 20 true))
(define ball-1-selected-rev (make-ball (+ INIT-POS-X 30) 
                                       (+ INIT-POS-Y 30) -20 true))
(define ball-1-moved (make-ball (+ INIT-POS-X 50) 
                                (+ INIT-POS-Y 30) 20 false))
(define ball-1-rt-edge (make-ball (- CANVAS-WIDTH BALL-RADIUS) 
                                  (+ INIT-POS-Y 30) 20 false))
(define ball-1-rt-edge-selected (make-ball (- CANVAS-WIDTH BALL-RADIUS) 
                                           (+ INIT-POS-Y 30) 20 true))
(define ball-1-rt-edge-rev (make-ball (- CANVAS-WIDTH BALL-RADIUS) 
                                      (+ INIT-POS-Y 30) -20 false))
(define ball-1-rt-edge-nxt (make-ball (- (- CANVAS-WIDTH BALL-RADIUS) 20) 
                                      (+ INIT-POS-Y 30) -20 false))
(define ball-1-nr-rt-edge (make-ball (- CANVAS-WIDTH (- BALL-RADIUS 2)) 
                                     (+ INIT-POS-Y 30) 20 false))
(define ball-1-lt-edge (make-ball BALL-RADIUS 
                                  (+ INIT-POS-Y 30) -20 false))
(define ball-1-lt-edge-selected (make-ball BALL-RADIUS 
                                           (+ INIT-POS-Y 30) -20 true))
(define ball-1-lt-edge-rev (make-ball BALL-RADIUS 
                                      (+ INIT-POS-Y 30) 20 false))
(define ball-1-lt-edge-nxt (make-ball (+ BALL-RADIUS 20) 
                                      (+ INIT-POS-Y 30) 20 false))
(define ball-1-nr-lt-edge (make-ball (- BALL-RADIUS 2) 
                                     (+ INIT-POS-Y 30) -20 false))
(define ball-1-top-edge (make-ball (+ INIT-POS-X 30) 
                                   BALL-RADIUS 20 true))
(define ball-1-top-edge-deselected (make-ball (+ INIT-POS-X 30) 
                                              BALL-RADIUS 20 false))
(define ball-1-bottom-edge (make-ball (+ INIT-POS-X 30) 
                                      (- CANVAS-HEIGHT BALL-RADIUS) 20 true))

(define world-1-ball-deselected (cons ball-1-deselected empty))
(define world-1-ball-selected (cons ball-1-selected empty))
(define world-1-ball-selected-rev (cons ball-1-selected-rev empty))
(define world-2-balls-deselected (cons ball-2-deselected 
                                       (cons ball-1-deselected empty)))
(define world-1-ball-selected-2-deselected 
  (cons ball-1-selected 
        (cons ball-2-deselected empty)))
(define world-1-ball-moved (cons ball-1-moved empty))
(define world-1-ball-rt-edge (cons ball-1-rt-edge empty))
(define world-1-ball-rt-edge-selected (cons ball-1-rt-edge-selected empty))
(define world-1-ball-rt-edge-rev (cons ball-1-rt-edge-rev empty))
(define world-1-ball-rt-edge-nxt (cons ball-1-rt-edge-nxt empty))
(define world-1-ball-nr-rt-edge (cons ball-1-nr-rt-edge empty))
(define world-1-ball-lt-edge (cons ball-1-lt-edge empty))
(define world-1-ball-lt-edge-selected (cons ball-1-lt-edge-selected empty))
(define world-1-ball-lt-edge-rev (cons ball-1-lt-edge-rev empty))
(define world-1-ball-lt-edge-nxt (cons ball-1-lt-edge-nxt empty))
(define world-1-ball-nr-lt-edge (cons ball-1-nr-lt-edge empty))
(define world-1-ball-top-edge (cons ball-1-top-edge empty))
(define world-1-ball-top-edge-deselected 
  (cons ball-1-top-edge-deselected empty))
(define world-1-ball-bottom-edge (cons ball-1-bottom-edge empty))

(define new-mouse-x-rt (- CANVAS-WIDTH BALL-RADIUS))
(define new-mouse-x-lt BALL-RADIUS)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;; render : World -> Scene
;; renders the balls.
;; passes the count of balls
;; to the place-balls-on-canvas function.
;; on the canvas.
;; Examples: See tests below
;; Strategy: Functional composition
(define (render l-balls)
  (place-balls-on-canvas (length l-balls) l-balls)
  )

;; place-balls-on-canvas : World -> Scene
;; places the balls and the count of balls 
;; on the canvas.
;; Examples: See tests below
;; Strategy: Structural decompostion using abstraction (on l-balls [World])
(define (place-balls-on-canvas cnt l-balls)
  (local (; Ball Scene -> Scene
          ; takes a ball and a scene
          ; and returns a scene with the ball 
          ; placed on the given scene
          (define (place-ball a-ball img)
            (place-image (ball-image a-ball) (pos-x-ball a-ball) 
                         (pos-y-ball a-ball) img)))
    (foldr place-ball (place-image-count cnt) l-balls)))

;; place-image-count : Number -> Scene
;; places the image of the count of balls
;; on the canvas.
;; Examples: See tests below
;; Strategy: Functional composition
(define (place-image-count cnt)
  (place-image (count-balls-text cnt) (- CANVAS-WIDTH 25) 10 CANVAS))

;; count-balls-text : Number -> Image
;; creates image of the count of balls
;; string.
;; Examples: See tests below
;; Strategy: Functional composition
(define (count-balls-text cnt)
  (text (count-balls-str cnt) 10 "red"))

;; count-balls-str : Number -> String
;; creates string to be displayed
;; ("Balls : count")
;; Examples: See tests below
;; Strategy: Functional composition
(define (count-balls-str cnt)
  (string-append "Balls : " (number->string cnt)))


;; ball-image : Ball -> Image
;; returns image of the ball
;; based on whether the ball
;; is selected or not.
;; Examples: See tests below
;; Strategy: structural decomposition (on Ball)
(define (ball-image a-ball)
  (cond [(ball-selected? a-ball)
         BALL-SELECTED]
        [else BALL-DESELECTED]))

;; pos-x-ball : Ball -> Number
;; returns x-coordinate of the ball.
;; Examples: See tests below
;; Strategy: structural decomposition [on Ball]
(define (pos-x-ball a-ball)
  (ball-pos-x a-ball))

;; pos-y-ball : Ball -> Number
;; returns y-coordinate of the ball.
;; Examples: See tests below
;; Strategy: structural decomposition [on Ball]
(define (pos-y-ball a-ball)
  (ball-pos-y a-ball))

;; tests
;; testing for display of balls and the count of balls on the canvas.

;; testing for display of 1 deselected ball on the canvas.
(check-equal? 
 (render world-1-ball-deselected) 
 (place-image BALL-DESELECTED (ball-pos-x ball-1-deselected) 
              (ball-pos-y ball-1-deselected) 
              (place-image (text "Balls : 1" 10 "red") 
                           (- CANVAS-WIDTH 25) 10 CANVAS))  
 "on the canvas, all the balls and the count of balls should be displayed")

;; testing for display of 1 selected and 1 deselected ball on the canvas.
(check-equal? 
 (render world-1-ball-selected-2-deselected) 
 (place-image BALL-SELECTED (ball-pos-x ball-1-selected) 
              (ball-pos-y ball-1-selected) 
              (place-image BALL-DESELECTED (ball-pos-x ball-2-deselected) 
                           (ball-pos-y ball-2-deselected) 
                           (place-image (text "Balls : 2" 10 "red")
                                        (- CANVAS-WIDTH 25) 10 CANVAS)))  
 "on the canvas, all the balls and the count of balls should be displayed")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; select-n-drag : World Number Number BallMouseEvent -> World
;; takes in the current list of balls, mouse x-coordinate,
;; mouse y-coordinate and ball mouse event.
;; returns list of balls operated on, based on the mouse event.
;; Examples: See tests below
;; Strategy: Structural decomposition (Enumeration on mev [BallMouseEvent])
(define (select-n-drag l-balls x y mev)
  (cond [(mouse=? mev "button-down") (select-ball l-balls x y)]
        [(mouse=? mev "button-up") (deselect-ball l-balls)]
        [(mouse=? mev "drag") (drag-ball l-balls x y)]
        [(mouse=? mev "leave") (deselect-ball l-balls)]
        [else l-balls]))

;; select-ball : World Number Number -> World
;; selects the ball on mouse-down event.
;; takes world, and x and y co-ordinates of the mouse pointer
;; and returns the world after updating the state of the ball 
;; which is selected.
;; Examples: See tests below
;; Strategy: Structural decomposition using abstraction (on l-balls [World])
(define (select-ball l-balls x y)
  (local (; Ball -> Ball
          ; returns ball as selected if the mouse pointer lies within
          (define (select-ball-whn-mouse-within a-ball)
            (select-ball-helper a-ball x y)))
    (map select-ball-whn-mouse-within l-balls)))


;; select-ball-helper : Ball Number Number -> Ball
;; takes a ball, and x and y co-ordinates of the mouse pointer
;; and returns the list of balls after updating the state of the ball 
;; which is selected.
;; Examples: See tests below
;; Strategy: Structural decomposition (on Ball)
(define (select-ball-helper a-ball x y)
  (cond [(and (< (- (ball-pos-x a-ball) BALL-RADIUS)  x  
                 (+ (ball-pos-x a-ball) BALL-RADIUS))
              (< (- (ball-pos-y a-ball) BALL-RADIUS)  y  
                 (+ (ball-pos-y a-ball) BALL-RADIUS))) 
         (selected-ball a-ball)]
        [else a-ball]))

;; selected-ball : Ball -> Ball
;; takes a ball and returns a selected ball
;; Examples: See tests below
;; Strategy: Structural decomposition [on Ball]
(define (selected-ball a-ball)
  (make-ball (ball-pos-x a-ball) (ball-pos-y a-ball)
             (ball-speed a-ball) true))

;; tests
;; testing for mouse "button-down" event on the world

;; testing for mouse "button-down" event on a ball 
;; within which the mouse pointer lies
(check-equal? 
 (select-n-drag world-1-ball-deselected 
                (+ INIT-POS-X 30) (+ INIT-POS-Y 30) "button-down") 
 world-1-ball-selected  
 "on mouse-down event, if the pointer lies within a ball, 
  the ball should be selected")

;; testing for mouse "button-down" event on a ball 
;; within which the mouse pointer does not lie
(check-equal? 
 (select-n-drag world-1-ball-deselected BALL-RADIUS 
                (+ INIT-POS-Y 30) "button-down") 
 world-1-ball-deselected  
 "on mouse-down event, if the pointer does not lie within a ball, 
  the ball should not be selected")

;; testing for mouse "button-down" event on 2 balls 
;; within none of which the mouse pointer does not lie
(check-equal? 
 (select-n-drag world-2-balls-deselected 
                BALL-RADIUS (+ INIT-POS-Y 30) "button-down") 
 world-2-balls-deselected 
 "on mouse-down event, if the pointer does not lie within any ball, 
  the balls should be selected")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; deselect-ball : World -> World
;; returns the list of balls after updating the state of the ball 
;; which is deselected.
;; Examples: See tests below
;; Strategy: Structural decomposition using abstraction (on l-balls [World])
(define (deselect-ball l-balls)
  (local (; Ball -> Ball
          ; returns ball as deselected
          (define (deselected-ball a-ball)
            (make-ball (ball-pos-x a-ball) (ball-pos-y a-ball)
                       (ball-speed a-ball) false)))
    (map deselected-ball l-balls)))

;; tests
;; testing for mouse "button-up" and "leave" events on the world

;; testing for mouse "button-up" event on a selected ball
(check-equal? 
 (select-n-drag world-1-ball-selected (+ INIT-POS-X 30) 
                (+ INIT-POS-Y 30) "button-up") 
 world-1-ball-deselected  
 "on button-up event on a ball, the ball should be deselected")

;; testing for mouse "button-up" event on a deselected ball
(check-equal? 
 (select-n-drag world-1-ball-top-edge (+ INIT-POS-X 30) 
                -10 "leave") 
 world-1-ball-top-edge-deselected  
 "when the mouse pointer leaves the canvas while dragging a  ball, 
  the ball should be left at the edge and deselected")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; drag-ball : World Number Number -> World
;; takes world, and mouse x and y co-ordinates and 
;; returns the world after updating the state of the ball 
;; which is dragged.
;; Examples: See tests below
;; Strategy: 
;; Structural decomposition using abstraction (on l-balls [World])
(define (drag-ball l-balls x y)
  (local (; Ball -> Ball
          ; takes a ball and returns ball 
          ; after setting new position
          (define (set-new-pos-on-drag a-ball)
            (set-new-pos-on-drag-helper a-ball x y)))
    (map set-new-pos-on-drag l-balls)))

;; set-new-pos-on-drag-helper : Ball Number Number -> Ball
;; takes a ball, and mouse x and y co-ordinates and
;; returns ball after setting new position
;; if ball is selected.
;; Examples: See tests below
;; Strategy: Structural decomposition (on Ball)
(define (set-new-pos-on-drag-helper a-ball x y)
  (cond [(ball-selected? a-ball) 
         (make-ball (new-pos-x-right-left-edge x) 
                    (new-pos-y-top-bottom-edge y) 
                    (ball-speed a-ball) 
                    true)]
        [else a-ball]))

;; new-pos-x-right-left-edge : Number -> Number
;; takes x-coordinate of the ball and returns 
;; new x-coordinate of the ball
;; if the ball is dragged to the right edge.
;; Examples: See tests below
;; Strategy: Functional composition
(define (new-pos-x-right-left-edge x)
  (cond [(>= (+ x BALL-RADIUS) CANVAS-WIDTH) 
         (- CANVAS-WIDTH BALL-RADIUS) ]
        [(<= (- x BALL-RADIUS) 0) BALL-RADIUS]
        [else x]))

;; new-pos-y-right-left-edge : Number -> Number
;; takes y-coordinate of the ball and returns 
;; new y-coordinate of the ball
;; if the ball is dragged to the left edge.
;; Examples: See tests below
;; Strategy: Functional composition
(define (new-pos-y-top-bottom-edge y)
  (cond [ (<= (- y BALL-RADIUS) 0) BALL-RADIUS]
        [ (>= (+ y BALL-RADIUS) CANVAS-HEIGHT)  
          (- CANVAS-HEIGHT BALL-RADIUS)]
        [else y]))


;; tests
;; testing for mouse "drag" event on the world

;; testing for mouse "drag" event on a selected ball
(check-equal? 
 (select-n-drag world-1-ball-selected new-mouse-x-rt 
                (+ INIT-POS-Y 30) "drag") 
 world-1-ball-rt-edge-selected  
 "when a ball is dragged, its new position should be that of the mouse")

(check-equal? 
 (select-n-drag world-1-ball-selected-rev new-mouse-x-lt 
                (+ INIT-POS-Y 30) "drag") 
 world-1-ball-lt-edge-selected  
 "when a ball is dragged, its new position should be that of the mouse")

;; testing for mouse "drag" event on a selected ball 
;; when mouse pointer is moved closed to the top edge
(check-equal? 
 (select-n-drag world-1-ball-selected (+ INIT-POS-X 30) 
                (/ BALL-RADIUS 2) "drag") 
 world-1-ball-top-edge  
 "when a ball is dragged and the mouse is close to the top edge, 
  the ball should be left at the top edge")

;; testing for mouse "drag" event on a selected ball 
;; when mouse pointer is moved closed to the bottom edge
(check-equal? 
 (select-n-drag world-1-ball-selected (+ INIT-POS-X 30) 
                (- CANVAS-HEIGHT (/ BALL-RADIUS 2)) "drag") 
 world-1-ball-bottom-edge  
 "when a ball is dragged and the mouse is close to the bottom edge, 
  the ball should be left at the bottom edge")

;; testing for mouse "drag" event on a deselected ball
(check-equal? 
 (select-n-drag world-1-ball-deselected (+ INIT-POS-X 30) 
                (- CANVAS-HEIGHT (/ BALL-RADIUS 2)) "drag") 
 world-1-ball-deselected  
 "of all the balls, the ones that are not selected should not move")


;; testing for mouse "move" event on the world
(check-equal? 
 (select-n-drag world-1-ball-deselected (+ INIT-POS-X 30) 
                10 "move") 
 world-1-ball-deselected  
 "when the mouse pointer moves over the canvas, not dragging a ball, 
  the balls should be unaffected")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; set-new-pos-ball-on-move : Ball -> Ball
;; takes a ball and returns the ball after setting 
;; new postion of the ball when on tick.
;; Examples: see tests below
;; Strategy: structural decomposition [on Ball]
(define (set-new-pos-ball-on-move a-ball)
  (cond [(ball-selected? a-ball) (stationary-ball a-ball)] 
        [else (set-new-pos-ball-not-stationary a-ball)]))

;; set-new-pos-ball-not-stationary : Ball -> Ball
;; takes a ball and returns the ball after setting 
;; new postion of the ball when not stationary.
;; Examples: see tests below
;; Strategy: structural decomposition [on Ball]
(define (set-new-pos-ball-not-stationary a-ball)
  (cond [(= (ball-pos-x a-ball) (- CANVAS-WIDTH BALL-RADIUS)) 
         (ball-new-pos-right-edge a-ball)]
        [(and (positive? (ball-speed a-ball)) 
              (> (+ (ball-pos-x a-ball) (ball-speed a-ball))
                 (- CANVAS-WIDTH BALL-RADIUS))) 
         (ball-new-pos-beyond-right-edge a-ball)]
        [else (set-new-pos-ball-not-rt-edge a-ball)]))

;; set-new-pos-ball-not-rt-edge : Ball -> Ball
;; takes a ball and returns the ball after setting 
;; new postion of the ball when not near or at the right edge
;; Examples: see tests below
;; Strategy: structural decomposition [on Ball]
(define (set-new-pos-ball-not-rt-edge a-ball)
  (cond [(= (ball-pos-x a-ball) BALL-RADIUS) 
         (ball-new-pos-left-edge a-ball)]
        [(and (negative? (ball-speed a-ball))
              (< (- (ball-pos-x a-ball) (- (ball-speed a-ball)))
                 BALL-RADIUS))
         (ball-new-pos-beyond-left-edge a-ball)]
        [else (ball-new-pos-elsewhere-canvas a-ball)]))

;; stationary-ball : Ball -? Ball
;; takes a ball and returns paused ball (selected)
;; Examples: see tests below
;; Strategy: structural decomposition [on Ball]
(define (stationary-ball a-ball)
  (make-ball (ball-pos-x a-ball) (ball-pos-y a-ball) 
             (ball-speed a-ball) true))

;; ball-new-pos-right-edge : Ball -> Ball
;; takes a ball and returns the ball with new position 
;; when at right edge.
;; Examples: see tests below
;; Strategy: structural decomposition [on Ball] 
(define (ball-new-pos-right-edge a-ball)
  (make-ball (- (ball-pos-x a-ball)
                (get-positive-speed (ball-speed a-ball))) 
             (ball-pos-y a-ball)
             (get-negative-speed (ball-speed a-ball)) false))

;; ball-new-pos-beyond-right-edge : Ball -> Ball
;; takes a ball and returns the ball with new position  
;; if right edge can be crossed.
;; Examples: see tests below
;; Strategy: structural decomposition [on Ball] 
(define (ball-new-pos-beyond-right-edge a-ball)
  (make-ball (- CANVAS-WIDTH BALL-RADIUS) (ball-pos-y a-ball)
             (- (ball-speed a-ball)) false))

;; ball-new-pos-left-edge : Ball -> Ball
;; takes a ball and returns the ball with new position  
;; when at left edge.
;; Examples: see tests below
;; Strategy: structural decomposition [on Ball] 
(define (ball-new-pos-left-edge a-ball)
  (make-ball (+ (ball-pos-x a-ball)
                (get-positive-speed (ball-speed a-ball))) 
             (ball-pos-y a-ball)
             (get-positive-speed (ball-speed a-ball)) false))

;; ball-new-pos-beyond-left-edge : Ball -> Ball
;; takes a ball and returns the ball with new position 
;; if left edge can be crossed.
;; Examples: see tests below
;; Strategy: structural decomposition (on Ball)  
(define (ball-new-pos-beyond-left-edge a-ball)
  (make-ball BALL-RADIUS (ball-pos-y a-ball)
             (- (ball-speed a-ball)) false))

;; ball-new-pos-elsewhere-canvas : Ball -> Ball
;; takes a ball and returns the ball with new position  
;; when ball is elsewhere on the canvas.
;; Examples: see tests below
;; Strategy: structural decomposition (on Ball) 
(define (ball-new-pos-elsewhere-canvas a-ball)
  (make-ball (+ (ball-pos-x a-ball)
                (ball-speed a-ball)) (ball-pos-y a-ball) 
                                     (ball-speed a-ball) false))

;; get-positive-speed: Number -> Number
;; takes speed and returns positive speed
;; Examples: see tests below
;; Strategy: functional composition 
(define (get-positive-speed speed)
  (if (positive? speed) speed (- speed)))

;; get-negative-speed: Number -> Number
;; takes speed and returns negative speed
;; Examples: see tests below
;; Strategy: functional composition 
(define (get-negative-speed speed)
  (if (negative? speed) speed (- speed)))

;; animate-world : World -> World
;; takes world and returns new world with new positions of the balls
;; on the canvas at a given speed (pixels/sec)
;; in either direction
;; on every tick.
;; returns updated list of balls.
;; Examples: see tests below
;; Strategy: Structural decomposition using abstraction (on l-balls [World])
(define (animate-world l-balls)
  (map set-new-pos-ball-on-move l-balls))

;; tests
;; testing for on-tick event on the world.

;; testing for on-tick event on a deselected ball 
(check-equal? 
 (animate-world world-1-ball-deselected) 
 world-1-ball-moved  
 "on tick, a ball should move horizontally by the given speed")

;; testing for on-tick event on a selected ball 
(check-equal? 
 (animate-world world-1-ball-selected) 
 world-1-ball-selected  
 "when a ball is selected, the ball should not glide on tick")

;; testing for on-tick event on a ball which has reached near the right edge 
(check-equal? 
 (animate-world world-1-ball-nr-rt-edge) 
 world-1-ball-rt-edge-rev  
 "on tick when a ball can cross the right edge, 
  the ball should move exactly to the right edge")

;; testing for on-tick event on a ball which is has reached the right edge 
(check-equal? 
 (animate-world world-1-ball-rt-edge-rev) 
 world-1-ball-rt-edge-nxt  
 "on tick when a ball, going toward left, is at the right edge, 
  the ball should reverse its direction")

;; testing for on-tick event on a ball which is at the right edge 
(check-equal? 
 (animate-world world-1-ball-rt-edge) 
 world-1-ball-rt-edge-nxt  
 "on tick when a ball,  going toward right, is at the right edge, 
  the ball should reverse its direction")

;; testing for on-tick event on a ball which has reached near the left edge 
(check-equal? 
 (animate-world world-1-ball-nr-lt-edge) 
 world-1-ball-lt-edge-rev  
 "on tick when a ball can cross the left edge, 
  the ball should move exactly to the left edge")

;; testing for on-tick event on a ball which has reached the left edge 
(check-equal? 
 (animate-world world-1-ball-lt-edge-rev) 
 world-1-ball-lt-edge-nxt  
 "on tick when a ball is at the right edge, 
  the ball should reverse its direction")

;; testing for on-tick event on a ball which is at the left edge 
(check-equal? 
 (animate-world world-1-ball-lt-edge) 
 world-1-ball-lt-edge-nxt  
 "on tick when a ball is at the right edge, 
  the ball should reverse its direction")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; add-ball : World BallKeyEvent -> World
;; takes world and ball key event and
;; adds a new ball to the world.
;; 'n' press: adds a new ball.
;; -- ignores other key events.
;; examples: see tests below 
;; Strategy: structural decomposition (Enumeration on kev [BallKeyEvent])
(define (add-ball l-balls kev) 
  (cond [(key=? kev "n")(world-after-n-press l-balls)]
        [else l-balls]))

;; world-after-n-press : World -> World
;; takes a world and adds a new ball to the world.
;; Examples: see tests below
;; Strategy: Structural decomposition (on l-balls [World])
(define (world-after-n-press l-balls)
  (cons (initialize-new-ball (first l-balls)) l-balls))

;; initialize-new-ball : World -> Ball
;; makes a new ball and initializes it
;; with speed taken from an existing ball.
;; Examples: see tests below
;; Strategy: Structural decomposition [on Ball]
(define (initialize-new-ball a-ball)
  (make-ball INIT-POS-X INIT-POS-Y 
             (get-positive-speed (ball-speed a-ball)) false))

;; tests
;; testing for key-press event on the world.

;; testing for "n" key press
(check-equal? 
 (add-ball world-1-ball-deselected "n") 
 world-2-balls-deselected  
 "on pressing 'n' key, a new ball should be added to the world")

;; testing for any other key press
(check-equal? 
 (add-ball world-1-ball-deselected "x") 
 world-1-ball-deselected  
 "on pressing any other key, the balls should be unaffected")


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
